package packet

import (
	"bytes"
	"fmt"
)

/* 数据包结构 */

/* 结构：
frame: head \ payload
packet: commandID , payload（ID+data）
*/

type MyPacket []byte

// 收到的包
type Submit struct {
	ID      string // 唯一标识
	Payload []byte
}

// 应答的包
type SubmitAck struct {
	ID     string
	Result byte
}

// 连接的包
type Conn struct {
	ID      string
	Payload []byte
}

type ConnAck struct {
	ID     string
	Result byte
}

// 接下来是解码编码的方法——frame帧解析出packet包

// 首先抽象出数据包的接口 -- 数据包肯定是多种的，这里应该抽象出数据包相关的方法
type Packet interface {
	Decode([]byte) error // 将frame解码出来 []byte -》 struct
	Encode() ([]byte, error)
}

// 将submit编码，因为自定义的协议比较简单，所以，packet的操作也是比较简单。但是该有的结构还是要有。
/* 在数据编码解码的过程中，肯定需要知道有没有成功，所以error是必要的 */
func (s *Submit) Encode() ([]byte, error) {
	// b := make([]byte, 0, len(s.Payload)+8)
	// b = append(b, []byte(s.ID)...)
	// b = append(b, s.Payload...)
	// return b, nil
	// 直接使用append的方法也行，但是感觉不优雅

	return bytes.Join( // join方法实现还是使用了append与copy
		[][]byte{
			[]byte(s.ID),
			s.Payload,
		},
		nil, // 这个sep参数是表示要在中间插入的一段[]byte
	), nil
}

// 将submit解码
func (s *Submit) Decode(b []byte) error {
	// 这里一定要紧扣住packet的结构
	s.ID = string(b[:8])
	s.Payload = b[8:]
	return nil
}

// Conn的部分，假设我们需要连接的时候带上一定的字段，那就还是一样，将信息编码解码到struct
// func (c *Conn)Encode()([]byte , error) {
// 	// 也就假设我们需要使用
// }

// 针对SubmitAck的情况
func (s *SubmitAck) Encode() ([]byte, error) {
	return bytes.Join(
		[][]byte{
			[]byte(s.ID),
			{s.Result},
		},
		nil,
	), nil
}
func (s *SubmitAck) Decode(pktBody []byte) error {
	s.ID = string(pktBody[:8])
	s.Result = pktBody[8]
	return nil
}

/* 上面已经编排好了对象（包括对象已经实现了接口），接下来应该是数据包packet的核心方法 */
// 为了方便，我们直接约定const作为标识
const (
	CommandConn = iota + 0x01
	CommandSubmit
)
const (
	CommandConnAck = iota + 0x80
	CommandSubmitAck
)

// 就是针对数据包，自己做解码,因为Packet是接口，所以，代表了我们定义的几个packet对象
func Decode(pkt []byte) (Packet, error) {
	// 根据协议检查类型
	// 然后根据类型调用解码
	commandID := pkt[0]
	pktBody := pkt[1:]

	switch commandID {
	case CommandSubmit:
		s := &Submit{}
		if err := s.Decode(pktBody); err != nil {
			return nil, err
		}
		return s, nil
	case CommandSubmitAck:
		s := &SubmitAck{}
		if err := s.Decode(pktBody); err != nil {
			return nil, err
		}
		return s, nil
	case CommandConn:
		return nil, nil
	case CommandConnAck:
		return nil, nil
	default:
		return nil, fmt.Errorf("unkonw CommandID :%v", commandID)
	}
}

// 这里是将Packet转为[]byte，然后这个[]byte是需要传递给frame的，需要加上类型标识
func Encode(pkt Packet) ([]byte, error) {
	var (
		pktHead byte
		pktBody []byte
		err     error
	)

	// 直接使用类型断言
	switch pkt.(type) {
	case *Submit:
		pktHead = CommandSubmit
		// pkt.Encode()
	case *SubmitAck:
		pktHead = CommandSubmitAck
	// case *Conn:
	// 	pktHead = CommandConn
	default:
		return nil, fmt.Errorf("unkonw Packet：%v", pkt)
	}
	pktBody, err = pkt.Encode()
	if nil != err {
		return nil, err
	}

	return bytes.Join(
		[][]byte{
			{pktHead},
			pktBody,
		},
		nil,
	), err
}

// 这里数据包就完成
/*
1. 从frame的角度看,frame更多是对象之间传递,利用的是IO流的接口——io.Writer/io.Reader.
- 中间涉及的编码解码工作，更多的是把数据包packet组装为传输的数据帧。——你定义的数据帧存在哪些结构需要补充，就在frame包中补充
- frame对接的是流对象，从流中拿，写入到流中。都是这样的思路。

2. 从packet的角度看，这里更多是数据与二进制的转化。——[]byte与strcut之间。packet的操作就是为了frame准备。
- 编码解码的操作就是[]byte与struct{}相互转化。
- 另外，packet的结构就是submit与submitAck，并且各自有decode与encode方法外，还有总体的encode、decode方法。
- 对象自己的编码与解码，针对的是[]byte与struct{}的转化。
- 针对总体的编码解码，是控制packet对象——因为还分了submit与submiutAck。针对传进来的不同对象，做不同的处理。

3. 往下走就是联动处理了
*/
